7

契机

用restify与express有一年多了,一直在考虑什么时候上koa与es6,看到《一起学koa》项目,决定以此为契机行动起来。首先我要完成的是对数据库操作的封装,将以前项目实践中的代码迁移到koa上来,于是决定先完成《一起学koa》中的mysql任务。

koa基本知识

koa依赖co,最新版基于promise实现。我们使用koa的时候都是通过use添加一个中间件,router也是一个个中间件,我们看一下use都做了什么?

app.use = function(fn){
  this.middleware.push(fn);
  return this;
};

它只是将参数保存起来,然后返回引用,最后由co完成调用,因此要求中间件中的异步调用都使用Promise形式。

实现方法一(co-mysql)

mysql库是以回调形式实现的,而koa中间件要求Promise形式,经过搜索,发现了co-mysql和mysql-co,这两个库的思路差不多,mysql-co封装度更高,并使用速度更快的mysql2,而co-mysql更简单,只是将mysql.query封装成Promise形式。下面是基于co-mysql的写法

var wrapper = require('co-mysql'),
  mysql = require('mysql');
var options = {
    host : 'localhost',
    port : 3306 ,
    database : 'test',
    user: 'root',
    password : 'rootroot'
};

var pool = mysql.createPool(options),
  p = wrapper(pool);

...
  var rows = yield p.query('SELECT 1');
  yield this.render('index', {
        title: rows[0].fieldName
    });
...
})();

实现方法二(promisify-node)

找到promisify-node库,可以将库整体转化为Promise形式,示例代码如下:

var promisify = require("promisify-node");
var db = promisify("myDbHelper");
...
var rows = yield db.getById('tableName', {id:1});
    yield this.render('index', {
        title: rows[0].fieldName
    });
...

实现方法三(thunkify、thunkify-wrap)

看tj/co说明的Yieldables部分说明如下:
The yieldable objects currently supported are:

  • promises

  • thunks (functions)

  • array (parallel execution)

  • objects (parallel execution)

  • generators (delegation)

  • generator functions (delegation)

因此使用thunkify也能够完成封装,thunkify-wrap是一个增强版的thunkify,不过看说明,这种方法在未来的发展中可能会被淘汰,大概的使用如下:

var genify = require('thunkify-wrap').genify;
var db = genify("myDbHelper");
...
var rows = yield db.getById('tableName', {id:1});
    yield this.render('index', {
        title: rows[0].fieldName
    });
...

实现方法四(直接方法)

直接改造原来express下的代码为Promise形式,参考了co-mysql,并仔细学习了Promise相关知识,完成了已有代码的改造,代码及说明如下:
dbHelper.js

var config = require('./dbconfig');

var options = {
    'host': config.db_host,
    'port': config.db_port,
    'database': config.db_name,
    'user': config.db_user,
    'password': config.db_passwd
}

var mysql = require('mysql');
var pool = mysql.createPool(options);

//内部对mysql的封装,执行sql语句
function execQuery(sql, values, callback) {
    var errinfo;
    pool.getConnection(function(err, connection) {
        if (err) {
            errinfo = 'DB-获取数据库连接异常!';
            throw errinfo;
        } else {
            var querys = connection.query(sql, values, function(err, rows) {
                release(connection);
                if (err) {
                    errinfo = 'DB-SQL语句执行错误:' + err;
                    callback(err);
                } else {
                    callback(null,rows);        //注意:第一个参数必须为null
                }
            });
        }
    });
}

function release(connection) {
    try {
        connection.release(function(error) {
            if (error) {
                console.log('DB-关闭数据库连接异常!');
            }
        });
    } catch (err) {}
}
//对外接口返回Promise函数形式
exports.getById = function(tablename, id){
    return new Promise(function(resolve, reject){
        var values = {id:id};
        var sql = 'select * from ?? where ?';
        execQuery(sql,[tablename, values], function(err, rows){
            if(err){
                reject(err);
            }else{
                resolve(rows);
            }
        })
    });
}

routes/index.js

var db = require("../dbHelper");
...
var rows = yield db.getById('tableName', {id:1});
    yield this.render('index', {
        title: rows[0].fieldName
    });
...

代码

请参考这个项目中的数据库操作部分,项目处于持续开发中,数据库示例部分取自该项目。

https://github.com/zhoutk/koadmin.git

小结

koa框架以co库为核心组织,很好的用generator来解决了回调函数问题。进行Promise接口形式包装的时候,要注意:回调函数要完全符合其要求的形式:

function(err, rows){
            if(err){
                reject(err);
            }else{
                resolve(rows);
            }
        })

zhoutk
2.6k 声望1.2k 粉丝

自由程序员,技术路线c,delphi,c++,c#,java,php,node.js,python,golang,typescript;超喜欢react.js的设计思路,全栈开发成为我的终极目标。开发机器macbook pro,或装ubuntu、fedora的机器,编程用vim...